package plp.orientadaObjetos2;

import java.io.File;

import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.orientadaObjetos1.comando.Comando;
import plp.orientadaObjetos1.excecao.declaracao.ClasseJaDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ClasseNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.execucao.EntradaInvalidaException;
import plp.orientadaObjetos1.excecao.execucao.EntradaNaoFornecidaException;
import plp.orientadaObjetos1.memoria.colecao.ListaValor;
import plp.orientadaObjetos2.cabecalho.Cabecalho;
import plp.orientadaObjetos2.declaracao.ConstrutorNaoDeclaradoException;
import plp.orientadaObjetos2.declaracao.ListaDeclaracaoOO;
import plp.orientadaObjetos2.memoria.AmbienteCompilacaoOO2;
import plp.orientadaObjetos2.memoria.AmbienteExecucaoOO2;
import plp.orientadaObjetos2.memoria.FuncaoJaDeclaradaException;
import plp.orientadaObjetos2.parser.OO2Parser;
import plp.orientadaObjetos2.util.MapPacoteArquivo;

/**
 * Classe que representa um programa na linguagem OO.
 */
public class Programa {

	public static final String FILE_EXTENSION = ".oo2";
	
	/**
	 * Nome do arquivo que contem o programa; 
	 */
	private String nomeArquivo;
	
	/**
	 * Cabecalho que contem o nome do pacote e os imports 
	 */
	private Cabecalho cabecalho;
	
    /**
     * lista de declaracoes OO
     */
    private ListaDeclaracaoOO declaracoesOO;
    /**
     * Comando executado pos a declaracao de classes
     */
    private Comando comando;
    
    /**
     * Construtor.
     * @param cabecalho cabecalho do arqruivo. 
     * @param decClasse A declaracao de classe(s)
     * @param comando O comando executado apÃ³s a declaraÃ§ao.
     */
    public Programa(Cabecalho cabecalho, ListaDeclaracaoOO dec, Comando comando){
        this.cabecalho = cabecalho;
        this.declaracoesOO = dec;
        this.comando = comando;
    }
    
    public void setNomeArquivo(String nomeArquivo){
    	this.nomeArquivo = nomeArquivo;
    }
    
    /**
     * Retorno o nome do arquivo
     * @return nome de arquivo com a respectiva extensão
     */
    public String getNomeArquivo(){
    	return this.nomeArquivo + FILE_EXTENSION;
    }

     /**
     * Executa o programa.
     *
     * @param ambiente o ambiente de execução.
     *
     * @return o ambiente depois de modificado pela execução
     * do programa.
     *
     * @exception EntradaNaoFornecidaException se não for fornecida
     *  a tail de valores de entrada do programa.
     * @throws ConstrutorNaoDeclaradoException 
     *
     */
    public ListaValor executar(AmbienteExecucaoOO2 ambiente)
        throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException,
               ObjetoNaoDeclaradoException, ObjetoJaDeclaradoException,
               ProcedimentoJaDeclaradoException,ProcedimentoNaoDeclaradoException,
               ClasseJaDeclaradaException, ClasseNaoDeclaradaException,
               EntradaNaoFornecidaException, EntradaInvalidaException, ConstrutorNaoDeclaradoException {
        if(ambiente == null)
            throw new EntradaNaoFornecidaException();
        ambiente.incrementa();
        if(declaracoesOO != null){
        	ambiente = declaracoesOO.elabora(ambiente);
        }
        ambiente = (AmbienteExecucaoOO2) comando.executar(ambiente);
        ambiente.restaura();
        return ambiente.getSaida();
    }

    /**
     * Realiza a verificacao de tipos do programa
     *
     * @param ambiente o ambiente de compilação.
     * @return <code>true</code> se o programa está bem tipado;
     *          <code>false</code> caso contrario.
     *
     * @exception EntradaNaoFornecidaException se não for fornecida
     *  a tail de valores de entrada do programa.
     * @throws ConstrutorNaoDeclaradoException 
     * @throws FuncaoJaDeclaradaException 
     *
     */
    public boolean checaTipo(AmbienteCompilacaoOO2 ambiente)
        throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException,
               ProcedimentoNaoDeclaradoException, ProcedimentoJaDeclaradoException,
               ClasseJaDeclaradaException, ClasseNaoDeclaradaException,
               EntradaNaoFornecidaException, ConstrutorNaoDeclaradoException, FuncaoJaDeclaradaException{
        boolean resposta = false;
        if(ambiente == null) {
            throw new EntradaNaoFornecidaException();
        }
        System.out.println("----------------------------------------------------");
        System.out.println("INICIO = Nome do arquivo avaliado => " + this.getNomeArquivo());
        
        ambiente.incrementa();
        
        if(declaracoesOO != null){
        	if (this.cabecalho.getPacote().checarTipo(ambiente)){
        		ambiente.adicionarArquivo(new MapPacoteArquivo(this.cabecalho.getPacote(), this.getNomeArquivo()));

        		resposta = this.cabecalho.checaTipo(ambiente)
        				&& this.declaracoesOO.checaTipo((AmbienteCompilacaoOO2) ambiente) 
        				&& this.comando.checaTipo(ambiente)
        				&& this.verificaCaminhoArquivo();
        	}
        } else {
        	resposta = comando.checaTipo(ambiente);
        }
        ambiente.restaura();
        
        System.out.println("FIM = Nome do arquivo avaliado => " + this.getNomeArquivo());
        System.out.println("----------------------------------------------------");
        return resposta;
    }
    
    public boolean verificaCaminhoArquivo(){
		String caminhoArquivo = OO2Parser.CAMINHO_RAIZ + File.separator
				+ this.cabecalho.getPacote().getNomePacote().replace('.', '\\')
				+ File.separator + this.getNomeArquivo();
		if(new File(caminhoArquivo).exists()){
			return true;
		}
		throw new RuntimeException("Arquivo: " + caminhoArquivo + " nao encontrado");
    }
    
}